"""
JSON Crunch core library.

Definitions
-----------
data:
    A tuple accepted by a method.
method:
    Any callable that accepts a list of data and returns a list of "aggregated" data.
key_methods:
    A dictionary that defines which methods to use for certain keys.
    Example:
        { 'date': minimum, 'values': average }
"""
import logging

from jsonlib import json, INDENT
from agg_methods import list_items, passthru, first
from merge_methods import merge_default, merge_first

import log_setup

DEFAULT = '*'

def dict_merge(dict1, dict2, merge_methods={}, parent_keys=() ):
    """
    Merges dict1 and dict2 by combining values for the same key as
    a list of values for that key.
    Returns merged dictionary.
    """
    logging.debug('BEGIN dict_merged %s and %s' % (dict1, dict2))
    merged = {}
    # Merge items, starting with keys from dict1.
    for key1, val1 in dict1.items():
        val2 = dict2.get(key1, None)
        if val2 is None:
            merged[key1] = val1
            logging.debug('Nothing to merge for "%s" = %s' % (key1, val1))
            continue
            
        # Lookup the correct method, by compound key, then by simple key.
        compound_key = tuple(list(parent_keys) + [ key1 ])
        method = merge_methods.get(compound_key, merge_methods.get(key1, merge_default))
        logging.debug('Merging 1 with method "%s"' % method.__name__)
        merged_val = method(val1, val2, merge_methods, parent_keys)
        merged[key1] = merged_val
        logging.debug('Adding merged value for "%s" = %s' % (key1, merged_val))
            
    # Add items from dict2 that haven't already been merged in.
    for key2, val2 in dict2.items():
        # Lookup the correct method, by compound key, then by simple key.
        compound_key = tuple(list(parent_keys) + [ key2 ])
        method = merge_methods.get(compound_key, merge_methods.get(key2, merge_first))  
        logging.debug('Merging 2 with method "%s"' % method.__name__)              
        new_val = method(val2, None, merge_methods, parent_keys)
        merged.setdefault(key2, new_val)
        logging.debug('Adding new value for "%s" = %s' % (key2, new_val))
        
    return merged
    
def dict_aggregate(subject, agg_methods, parent_keys=() ):
    """
    Aggregates values from (already merged) subject that are lists, 
    using the method defined for their keys in methods. If no method is 
    defined for a key, then its value is copied as-is, with no change.
    """
    default_agg_method = agg_methods.get(DEFAULT, passthru)
    result = {}    
    for key, value in subject.items():
        # Lookup the correct method, by compound key, then by simple key.
        compound_key = tuple(list(parent_keys) + [ key ])
        method = agg_methods.get(compound_key, agg_methods.get(key, default_agg_method))
        logging.debug('Aggregating %s with method "%s"' % (compound_key, method.__name__))
        
        # Aggregate it using the method we just found.
        if type(value) is dict:
            agg_value = dict_aggregate(value, agg_methods, compound_key)
        elif type(value) is list:
            if method == list_items:
                # Aggregate items in series individually.
                tmp = []
                for item in value:
                    tmp.append(dict_aggregate(item, agg_methods, compound_key))
                agg_value = tmp
            else:
                agg_value = method(value)
        else:
            agg_value = value
        result[key] = agg_value
    logging.debug('Aggregated result: %s' % result)
    return result

def aggregate(filenames, merge_methods={}, agg_methods={}):
    """
    Returns a JSON string containing data loaded from filenames and
    aggregated using methods.
    """
    merged = {}
    for filename in filenames:        
        f = open(filename, 'r')
        data = json.load(f)
        f.close()
        merged = dict_merge(merged, data, merge_methods)
        
    result = dict_aggregate(merged, agg_methods)
    aggregated_json = json.dumps(result, sort_keys=True, indent=INDENT)
    return aggregated_json
